MaxModLoader.Create(MaxMod_Loader_Mod,"Protracker ~q.mod~q","mod")

Function MaxMod_Loader_Mod:TMusic(Url:Object)

	Local tracker$, songname$, txt$
	Local samplescount:Int = 31
	Local bin1$, bin2$, bin3$, bin4$
	Local bigpat:Int,seq:Int
	Local freq:Int, sampleno:Int,samplefx:Int
	Local c:Int, n:Int
	Local songlength:Int, songloop:Int
	Local sam:Int, Sname:String
	Local size:Int,finetune:Int,volume:Int
	Local repeatpoint:Int,repeatlength:Int,repeatend:Int
    Local offset:Int
	Local DataBank:TBank
	Local ThisMOD	:MaxMod_Mixer = New MaxMod_Mixer
	Local Inst		:MaxMod_Instrument
	Local Sample	:MaxMod_Sample
    ThisMod.WriteBug("Loading MOD format file...~n")
	
	Local f:TStream
	Local bigf:TStream
	If TStream(Url)
		f	 = TStream(Url)
		bigf = BigEndianStream( f:TStream )	; If Not bigf Return Null
	Else
    	f 	 = ReadStream(url)				; If Not f Return Null
		bigf = BigEndianStream( f:TStream )	; If Not bigf Return Null
	EndIf

	ThisMod.SongName = Trim(ReadString(f,20))
  	
'   ---------------------------------------------------------------------------------------------
'	Find out what tracker was used
    SeekStream(f,1080) ; txt$ = ReadString(f,4)
	ThisMod.WriteBug("TAG = ~q"+Trim(txt$)+"~q   $"+Right(Hex(txt[0]),2)+".$"+Right(Hex(txt[1]),2)+".$"+Right(Hex(txt[2]),2)+".$"+Right(Hex(txt[3]),2)+"~n")
    Select txt$
        Case "M.K." ; ThisMOD.TrackerName$ = "Protracker 4 channel"		 ; ThisMOD.ActiveChannels = 4
        Case "M!K!" ; ThisMOD.TrackerName$ = "Protracker 4 channel"		 ; ThisMOD.ActiveChannels = 4
        Case "FLT4" ; ThisMOD.TrackerName$ = "Startracker 4 channel"	 ; ThisMOD.ActiveChannels = 4
'        Case "FLT8" ; ThisMOD.TrackerName$ = "Startracker 8 channel" 	 ; ThisMOD.ActiveChannels = 8
        Case "2CHN" ; ThisMOD.TrackerName$ = "Fasttracker 2 channel"	 ; ThisMOD.ActiveChannels = 2
        Case "4CHN" ; ThisMOD.TrackerName$ = "Fasttracker 4 channel"	 ; ThisMOD.ActiveChannels = 4
        Case "6CHN" ; ThisMOD.TrackerName$ = "Fasttracker 6 channel" 	 ; ThisMOD.ActiveChannels = 6
        Case "8CHN" ; ThisMOD.TrackerName$ = "Fasttracker 8 channel" 	 ; ThisMOD.ActiveChannels = 8
        Case "10CH" ; ThisMOD.TrackerName$ = "Fasttracker 10 channel" 	 ; ThisMOD.ActiveChannels = 10
        Case "12CH" ; ThisMOD.TrackerName$ = "Fasttracker 12 channel" 	 ; ThisMOD.ActiveChannels = 12
        Case "14CH" ; ThisMOD.TrackerName$ = "Fasttracker 14 channel" 	 ; ThisMOD.ActiveChannels = 14
        Case "16CH" ; ThisMOD.TrackerName$ = "Fasttracker 14 channel" 	 ; ThisMOD.ActiveChannels = 16
        Case "18CH" ; ThisMOD.TrackerName$ = "Fasttracker 18 channel" 	 ; ThisMOD.ActiveChannels = 18
        Case "20CH" ; ThisMOD.TrackerName$ = "Fasttracker 20 channel" 	 ; ThisMOD.ActiveChannels = 20
        Case "22CH" ; ThisMOD.TrackerName$ = "Fasttracker 22 channel" 	 ; ThisMOD.ActiveChannels = 22
        Case "24CH" ; ThisMOD.TrackerName$ = "Fasttracker 24 channel" 	 ; ThisMOD.ActiveChannels = 24
        Case "26CH" ; ThisMOD.TrackerName$ = "Fasttracker 26 channel" 	 ; ThisMOD.ActiveChannels = 26
        Case "28CH" ; ThisMOD.TrackerName$ = "Fasttracker 28 channel" 	 ; ThisMOD.ActiveChannels = 28
        Case "30CH" ; ThisMOD.TrackerName$ = "Fasttracker 30 channel" 	 ; ThisMOD.ActiveChannels = 30
        Case "32CH" ; ThisMOD.TrackerName$ = "Fasttracker 32 channel" 	 ; ThisMOD.ActiveChannels = 32
        Case "TDZ1" ; ThisMOD.TrackerName$ = "TakeTracker 1 channel" 	 ; ThisMOD.ActiveChannels = 1
        Case "TDZ2" ; ThisMOD.TrackerName$ = "TakeTracker 2 channel" 	 ; ThisMOD.ActiveChannels = 2
        Case "TDZ3" ; ThisMOD.TrackerName$ = "TakeTracker 3 channel" 	 ; ThisMOD.ActiveChannels = 3
        Case "5CHN" ; ThisMOD.TrackerName$ = "TakeTracker 5 channel" 	 ; ThisMOD.ActiveChannels = 5
        Case "7CHN" ; ThisMOD.TrackerName$ = "TakeTracker 7 channel" 	 ; ThisMOD.ActiveChannels = 7
        Case "9CHN" ; ThisMOD.TrackerName$ = "TakeTracker 9 channel" 	 ; ThisMOD.ActiveChannels = 9
        Case "11CH" ; ThisMOD.TrackerName$ = "TakeTracker 11 channel" 	 ; ThisMOD.ActiveChannels = 11
        Case "13CH" ; ThisMOD.TrackerName$ = "TakeTracker 13 channel" 	 ; ThisMOD.ActiveChannels = 13
        Case "15CH" ; ThisMOD.TrackerName$ = "TakeTracker 15 channel" 	 ; ThisMOD.ActiveChannels = 15
        Case "16CN" ; ThisMOD.TrackerName$ = "Taketracker 16 channel" 	 ; ThisMOD.ActiveChannels = 16
        Case "32CN" ; ThisMOD.TrackerName$ = "Taketracker 32 channel" 	 ; ThisMOD.ActiveChannels = 32
        Case "CD81" ; ThisMOD.TrackerName$ = "Atari oktalyzer 8 channel" ; ThisMOD.ActiveChannels = 8
        Case "OCTA" ; ThisMOD.TrackerName$ = "Atari oktalyzer 8 channel" ; ThisMOD.ActiveChannels = 8
		Case "N.T." ; ThisMOD.TrackerName$ = "NoiseTracker 32 channel" 	 ; ThisMOD.ActiveChannels = 32
		Case "CD81" ; ThisMOD.TrackerName$ = "Falcon 8 channel" 		 ; ThisMOD.ActiveChannels = 8
'        Case "" 	; ThisMOD.TrackerName$ = "15-instrument 4 channel" 	 ; ThisMOD.ActiveChannels = 4	; samplescount = 15
'        Default 	; ThisMOD.TrackerName$ = "Unknown!"					 ; ThisMOD.ActiveChannels = 4   ; samplescount = 15
'			ThisMod.WriteBug("UNKNOWN TRACKER TAG~n")
		Default 	; Return Null
    EndSelect
	ThisMOD.Init(ThisMOD.ActiveChannels)
    ThisMod.WriteBug("Format = "+ThisMOD.TrackerName$+"~n")
    ThisMod.WriteBug("Channels = "+ThisMOD.ActiveChannels+"~n")

'   ---------------------------------------------------------------------------------------------
'   READ SAMPLES
    ThisMOD.ActiveSamples = samplescount
    ThisMod.WriteBug("Instruments = "+samplescount+"~n")

	Local samsize:Int[ThisMod.MaxInstruments]

	ThisMod.Instrument		= New MaxMod_Instrument[samplescount+1]
    For sam=1 To samplescount

		Inst 						= New MaxMod_Instrument
		ThisMod.Instrument[sam] 	= Inst
  
        SeekStream(f,offset+20) 	; Inst.Name$ 	= Trim(ReadString(f,22))
        SeekStream(bigf,offset+42)  ; samsize[sam]	= ReadShort(bigf) Shl 1				' BIG ENDIAN
        SeekStream(f,offset+44) 	; finetune 		= ReadByte(f)
        SeekStream(f,offset+45) 	; volume 		= ReadByte(f)
        SeekStream(bigf,offset+46) 	; repeatpoint 	= ReadShort(bigf) Shl 1				' BIG ENDIAN
        SeekStream(bigf,offset+48) 	; Repeatlength 	= ReadShort(bigf) Shl 1				' BIG ENDIAN

		If finetune>16 Then finetune:-256
		If finetune>7 Then finetune:-16
		If volume<0 Then volume=0
		If volume>64 Then Volume=64

		If samsize[sam]>2
			Inst.SampleCount		= 1
			Sample 					= New MaxMod_Sample
	        Sample.Name 			= Inst.Name$
	        Sample.Volume 			= (1.0/64)*volume
	        Sample.Finetune			= (1!/8)*finetune
			If repeatlength>2
				Sample.Loop 		= True
				Sample.LoopStart 	= repeatpoint
				Sample.LoopLength   = repeatlength
			EndIf
			Inst.Sample[0] = Sample
	    	ThisMod.WriteBug("Sample "+sam+" ~q"+Sname+"~q "+samsize[sam]+"k~n")
		EndIf
        offset:+30

    Next

'   ---------------------------------------------------------------------------------------------
'   READ SEQUENCE
'	If samplescount = 31 ; SeekStream(f,950) ; Else ; SeekStream(f,470) '?   
	songlength = ReadByte(f)
	songloop   = ReadByte(f)
	ThisMOD.SongPattern = New Int[songlength+1]
	For n=0 To songlength
        ThisMOD.SongPattern[n] = ReadByte(f)
        If ThisMOD.SongPattern[n]>bigpat Then bigpat = ThisMOD.SongPattern[n]
    Next
	For n = songlength+1 Until 128 ; ReadByte(f) ; Next		' read the rest of empty list
    ThisMOD.ActiveSongPatterns  = songlength-1
    ThisMOD.CurrentPattern = ThisMOD.SongPattern[0]

	ReadInt(f) ' skip file format ID tag

'   ---------------------------------------------------------------------------------------------
'   READ PATTERNS
'    If samplescount=31 ; SeekStream(f,1084) ; Else ; SeekStream(f,600-1)
    Local Track:MaxMod_Track
	ThisMod.Pattern = New MaxMod_Pattern[bigpat+1]
    ThisMod.WriteBug("Patterns = "+bigpat+"~n")
	Local v1:Int, v2:Int, v3:Int
	For seq = 0 To bigpat
		ThisMod.Pattern[seq] = MaxMod_Pattern.Create(ThisMOD,ThisMOD.ActiveChannels, 64)
		ThisMod.Pattern[seq].Title = "Pattern "+seq
        For n=0 Until 64
            For c = 0 Until ThisMOD.ActiveChannels
				Track 		  = ThisMod.Pattern[seq].Track[c]
				
				v1 				= ReadByte(f)
				freq 			= ((v1 & %00001111) Shl 8)+ReadByte(f)
				v3 				= ReadByte(f)
				Track.Inst[n] 	= (v1 & %11110000)+((v3 & %11110000) Shr 4)
				Track.FX[n]	  	= v3 & %00001111
				Track.FXV[n]  	= ReadByte(f)

				' fix protracker pattern break commands to proper hex values
				If Track.FX[n]=$D Then Track.FXV[n] = (10*Int("$"+Left(Right(Hex(Track.FXV[n]),2),1))) + (Int("$"+Right(Hex(Track.FXV[n]),1)))
				
				Select (c Mod 3)
					Case 0,3 ; Track.Vol[n]  = $c0
					Case 1,2 ; Track.Vol[n]  = $cF
				EndSelect
				If Freq 
					Track.Note[n] = ConvertAmigaFrequency(Freq)
            		If Track.Note[n] = 0 Then ThisMod.WriteBug("NO MATCH FOUND FOR NOTE ~q"+Freq+"~q - DISCARDED! (Pattern "+seq+", Track "+c+",Line "+n+")~n") ; Print "note not found!"
				EndIf
			Next
        Next
		ThisMod.Pattern[seq].UpdateText
    Next

'   ---------------------------------------------------------------------------------------------
'   READ SAMPLE DATA
    For sam=1 To samplescount
        If samsize[sam]>4
			DataBank = CreateBank(samsize[sam])
			DataBank.Read(f,0,samsize[sam])
			ThisMOD.Instrument[sam].Sample[0].ConvertSampleFromBank(DataBank, 1)
        EndIf
    Next

	CloseFile f
	CloseFile bigf
    ThisMod.WriteBug("Load complete.~n")
	Return ThisMOD

EndFunction



' CONVERTS AN PROTRACKER NOTE INTO A MAXMOD NOTE -----------------------------------------------------
Function ConvertAmigaFrequency:Int(AmigaFreq:Int)
	Local PeriodTable:Int[] = 	[ 6868, 6464, 6096, 5760, 5424, 5120, 4832, 4560, 4304, 4064, 3840, 3624, ..
								  3424, 3232, 3048, 2880, 2712, 2560, 2416, 2280, 2152, 2032, 1920, 1812, ..
								  1712, 1616, 1525, 1440, 1357, 1281, 1209, 1141, 1077, 1017, 0961, 0907, ..
								  0856, 0808, 0762, 0720, 0678, 0640, 0604, 0570, 0538, 0508, 0480, 0453, ..
								  0428, 0404, 0381, 0360, 0339, 0320, 0302, 0285, 0269, 0254, 0240, 0226, ..
								  0214, 0202, 0190, 0180, 0170, 0160, 0151, 0143, 0135, 0127, 0120, 0113, ..
								  0107, 0101, 0095, 0090, 0085, 0080, 0076, 0071, 0067, 0064, 0060, 0057, ..
								  0054, 0051, 0048, 0045, 0043, 0040, 0038, 0036, 0034, 0032, 0030, 0029, ..
								  0027, 0026, 0024, 0023, 0022, 0020, 0019, 0018, 0017, 0016, 0015, 0014  ]
	Local n:Int
	For n=0 Until Len(PeriodTable)
		If AmigaFreq => PeriodTable[n] Then Return n
	Next
	Return 0
EndFunction



Function MaxMod_Create_Mod:TMusic()
	Local ThisMOD:MaxMod_Mixer  = New MaxMod_Mixer
	ThisMOD.ActiveChannels 		= 4
	ThisMOD.ActiveSamples 		= 31
	ThisMOD.ActiveSongPatterns 	= 1
	ThisMOD.Init(ThisMOD.ActiveChannels)
	ThisMod.Pattern[0] 			= MaxMod_Pattern.Create(ThisMOD,4,64)
	Return ThisMOD
EndFunction
